import { Vector3, Quaternion, Object3D, Matrix4 } from '../build/three.module.js';

const _position = new Vector3();
const _quaternion = new Quaternion();
const _scale = new Vector3();

class CSS3DObject extends Object3D {

	constructor() {

		super();

	}

	copy(source, recursive) {

		super.copy(source, recursive);

		return this;

	}

}

CSS3DObject.prototype.isCSS3DObject = true;

class CSS3DSprite extends CSS3DObject {

	constructor() {

		super();

		this.rotation2D = 0;

	}

	copy(source, recursive) {

		super.copy(source, recursive);

		this.rotation2D = source.rotation2D;

		return this;

	}

}

CSS3DSprite.prototype.isCSS3DSprite = true;

//

const _matrix = new Matrix4();
const _matrix2 = new Matrix4();

class CSS3DRenderer {

	constructor(callbacks) {

		const _this = this;

		_this.callbacks = callbacks;

		let _width, _height;
		let _widthHalf, _heightHalf;

		const cache = {
			camera: { fov: 0, style: '' },
			objects: new WeakMap()
		};

		this.getSize = function () {

			return {
				width: _width,
				height: _height
			};

		};

		this.render = function (scene, camera) {

			const fov = camera.projectionMatrix.elements[5] * _heightHalf;

			if (cache.camera.fov !== fov) {

				// domElement.style.perspective = camera.isPerspectiveCamera ? fov + 'px' : '';
				cache.camera.fov = fov;

				_this.callbacks.setCameraStyle && _this.callbacks.setCameraStyle({
					perspective: camera.isPerspectiveCamera ? fov + 'px' : '',
				});

			}

			if (scene.autoUpdate === true) scene.updateMatrixWorld();
			if (camera.parent === null) camera.updateMatrixWorld();

			let tx, ty;

			if (camera.isOrthographicCamera) {

				tx = - (camera.right + camera.left) / 2;
				ty = (camera.top + camera.bottom) / 2;

			}

			const cameraCSSMatrix = camera.isOrthographicCamera ?
				'scale(' + fov + ')' + 'translate(' + epsilon(tx) + 'px,' + epsilon(ty) + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse) :
				' translateZ(' + fov + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse);

			const style = cameraCSSMatrix +
				',translate(' + _widthHalf + 'px,' + _heightHalf + 'px)';

			if (cache.camera.style !== style) {

				_this.callbacks.setCameraStyle && _this.callbacks.setCameraStyle({
					transform: style,
				});

				// cameraElement.style.transform = style;

				cache.camera.style = style;

			}

			renderObject(scene, scene, camera);

		};

		this.setSize = function (width, height) {

			_width = width;
			_height = height;
			_widthHalf = _width / 2;
			_heightHalf = _height / 2;

		};

		function epsilon(value) {

			return Math.abs(value) < 1e-10 ? 0 : value;

		}

		function getCameraCSSMatrix(matrix) {

			const elements = matrix.elements;

			return ',matrix3d(' +
				epsilon(elements[0]) + ',' +
				epsilon(- elements[1]) + ',' +
				epsilon(elements[2]) + ',' +
				epsilon(elements[3]) + ',' +
				epsilon(elements[4]) + ',' +
				epsilon(- elements[5]) + ',' +
				epsilon(elements[6]) + ',' +
				epsilon(elements[7]) + ',' +
				epsilon(elements[8]) + ',' +
				epsilon(- elements[9]) + ',' +
				epsilon(elements[10]) + ',' +
				epsilon(elements[11]) + ',' +
				epsilon(elements[12]) + ',' +
				epsilon(- elements[13]) + ',' +
				epsilon(elements[14]) + ',' +
				epsilon(elements[15]) +
				')';

		}

		function getObjectCSSMatrix(matrix) {

			const elements = matrix.elements;
			const matrix3d = ',matrix3d(' +
				epsilon(elements[0]) + ',' +
				epsilon(elements[1]) + ',' +
				epsilon(elements[2]) + ',' +
				epsilon(elements[3]) + ',' +
				epsilon(- elements[4]) + ',' +
				epsilon(- elements[5]) + ',' +
				epsilon(- elements[6]) + ',' +
				epsilon(- elements[7]) + ',' +
				epsilon(elements[8]) + ',' +
				epsilon(elements[9]) + ',' +
				epsilon(elements[10]) + ',' +
				epsilon(elements[11]) + ',' +
				epsilon(elements[12]) + ',' +
				epsilon(elements[13]) + ',' +
				epsilon(elements[14]) + ',' +
				epsilon(elements[15]) +
				')';

			return ',translate(-50%,-50%)' + matrix3d;

		}

		function renderObject(object, scene, camera, cameraCSSMatrix) {

			if (object.isCSS3DObject) {

				object.onBeforeRender(_this, scene, camera);

				let style;

				if (object.isCSS3DSprite) {

					// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/

					_matrix.copy(camera.matrixWorldInverse);
					_matrix.transpose();

					if (object.rotation2D !== 0) _matrix.multiply(_matrix2.makeRotationZ(object.rotation2D));

					object.matrixWorld.decompose(_position, _quaternion, _scale);
					_matrix.setPosition(_position);
					_matrix.scale(_scale);

					_matrix.elements[3] = 0;
					_matrix.elements[7] = 0;
					_matrix.elements[11] = 0;
					_matrix.elements[15] = 1;

					style = getObjectCSSMatrix(_matrix);

				} else {

					style = getObjectCSSMatrix(object.matrixWorld);

				}

				const cachedObject = cache.objects.get(object);

				if (cachedObject === undefined || cachedObject.style !== style) {

					_this.callbacks.setObjectStyle && _this.callbacks.setObjectStyle(object, {
						transform: style,
						display: object.visible ? 'block' : 'none'
					});

					const objectData = { style: style };
					cache.objects.set(object, objectData);
				}

				object.onAfterRender(_this, scene, camera);

			}

			for (let i = 0, l = object.children.length; i < l; i++) {

				renderObject(object.children[i], scene, camera);

			}

		}

	}

}

export { CSS3DObject, CSS3DRenderer, CSS3DSprite };
